package com.koron.plan.web.patrollineplan.service.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageInfo;
import com.koron.bean.base.Response;
import com.koron.bean.plan.patrol.dto.PatrolDto;
import com.koron.bean.plan.patrol.po.PatrolEquipmentItemPo;
import com.koron.bean.plan.patrol.po.PatrolEquipmentParamsPo;
import com.koron.bean.plan.patrol.po.PatrolEquipmentPo;
import com.koron.bean.plan.patrol.po.PatrolUserPo;
import com.koron.bean.plan.patrol.vo.PatrolEnvCheckVo;
import com.koron.bean.plan.patrol.vo.PatrolEquipmentVo;
import com.koron.bean.plan.patrol.vo.PatrolPointVo;
import com.koron.bean.query.PageQuery;
import com.koron.bean.system.staff.vo.EamUser;
import com.koron.common.core.config.ThreadLocalContext;
import com.koron.common.core.util.OrgCacheUtils;
import com.koron.common.core.util.StringUtils;
import com.koron.common.task.web.entity.CronTask;
import com.koron.common.task.web.entity.XxlJobInfo;
import com.koron.common.task.web.service.XxlJobService;
import com.koron.plan.config.Constants;
import com.koron.plan.web.common.CommonUtils;
import com.koron.plan.web.feign.EquipmentFeign;
import com.koron.plan.web.feign.OrderFeign;
import com.koron.plan.web.patrollineplan.bean.convertor.PatrolPlanLineConvertor;
import com.koron.plan.web.patrollineplan.bean.dto.PlanLinePatrolDTO;
import com.koron.plan.web.patrollineplan.bean.dto.PlanLinePatrolEquipment;
import com.koron.plan.web.patrollineplan.bean.dto.PlanLinePatrolItemDTO;
import com.koron.plan.web.patrollineplan.bean.po.PlanLinePatrol;
import com.koron.plan.web.patrollineplan.bean.po.PlanLinePatrolItem;
import com.koron.plan.web.patrollineplan.bean.po.PlanLinePatrolWorker;
import com.koron.plan.web.patrollineplan.bean.query.PlanLinePatrolQuery;
import com.koron.plan.web.patrollineplan.mapper.PlanLinePatrolItemMapper;
import com.koron.plan.web.patrollineplan.mapper.PlanLinePatrolMapper;
import com.koron.plan.web.patrollineplan.service.PlanLinePatrolItemService;
import com.koron.plan.web.patrollineplan.service.PlanLinePatrolService;
import com.koron.plan.web.patrollineplan.service.PlanLinePatrolWorkerService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.koron.ebs.mybatis.SessionFactory;
import org.koron.ebs.mybatis.TaskAnnotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 线路巡视计划(PlanLinePatrol)表服务实现类
 *
 * @author jzp
 * @since 2022-11-24 14:24:19
 */
@Slf4j
@Service
public class PlanLinePatrolServiceImpl implements PlanLinePatrolService {

    @Autowired
    private PlanLinePatrolWorkerService planLinePatrolWorkerService;

    @Autowired
    private PlanLinePatrolItemService planLinePatrolItemService;

    @Autowired
    private XxlJobService xxlJobService;

    @Autowired
    private OrgCacheUtils orgUtils;

    @Autowired
    private EquipmentFeign equipmentFeign;

    @Autowired
    private OrderFeign orderFeign;

    @Autowired
    private PatrolPlanLineConvertor convertor;

    /**
     * 通过ID查询单条数据
     *
     */
    @Override
    @TaskAnnotation("getById")
    public PlanLinePatrol getById(SessionFactory factory, String id) {
        PlanLinePatrolMapper mapper = factory.getMapper(PlanLinePatrolMapper.class);
        return mapper.selectById(id);
    }

    /**
     * 通过ID查询单条数据
     *
     */
    @Override
    @TaskAnnotation("getDtoById")
    public PlanLinePatrolDTO getDtoById(SessionFactory factory, String id) {
        // 获取主表数据
        PlanLinePatrol planLinePatrol = getById(factory, id);
        PlanLinePatrolDTO planLinePatrolDTO = BeanUtil.toBean(planLinePatrol, PlanLinePatrolDTO.class);
        // 获取人员数据
        List<PlanLinePatrolWorker> planLinePatrolWorkers = planLinePatrolWorkerService.listByPlanId(factory, id);
        planLinePatrolDTO.setWorkers(planLinePatrolWorkers);
        // 获取巡视点数据
        List<PlanLinePatrolItemDTO> planLinePatrolItemDTOS = planLinePatrolItemService.listDtoByPlanId(factory, id);
        planLinePatrolDTO.setItemList(planLinePatrolItemDTOS);
        // 获取定时任务数据
        planLinePatrolDTO.setCronTask(xxlJobService.getCronTaskById(id));
        return planLinePatrolDTO;
    }

    /**
     * 查询所有数据
     *
     */
    @Override
    @TaskAnnotation("list")
    public List<PlanLinePatrol> list(SessionFactory factory, PlanLinePatrolQuery query) {
        PlanLinePatrolMapper mapper = factory.getMapper(PlanLinePatrolMapper.class);
        return mapper.selectList(query);
    }

    @Override
    @TaskAnnotation("page")
    public PageInfo<PlanLinePatrol> page(SessionFactory factory, PageQuery pageQuery, PlanLinePatrolQuery query) {
        return PlanLinePatrolService.super.page(factory, pageQuery, query);
    }

    /**
     * 新增数据
     *
     */
    @Override
    @TaskAnnotation("save")
    public boolean save(SessionFactory factory, PlanLinePatrol planLinePatrol) {
        PlanLinePatrolMapper mapper = factory.getMapper(PlanLinePatrolMapper.class);
        planLinePatrol.setCode(StringUtils.isBlank(planLinePatrol.getCode()) ? CommonUtils.getCodeNo(Constants.LINE_PATROL_PREFIX, 3) : planLinePatrol.getCode());
        planLinePatrol.setDeptName(orgUtils.getOrgNameById(planLinePatrol.getDeptId()));
        planLinePatrol.setPlanDeptName(orgUtils.getOrgNameById(planLinePatrol.getPlanDeptId()));
        planLinePatrol.setPatrolDeptName(orgUtils.getOrgNameById(planLinePatrol.getPatrolDeptId()));
        return mapper.insertOrUpdate(planLinePatrol) > 0;
    }

    @Override
    @TaskAnnotation("saveDto")
    public boolean saveDto(SessionFactory factory, PlanLinePatrolDTO planLinePatrolDTO) {
        // 保存数据
        boolean b = save(factory, planLinePatrolDTO);

        // 清除并保存巡视人员数据
        Assert.notEmpty(planLinePatrolDTO.getWorkers(), "巡视人员不能为空");
        boolean b1 = planLinePatrolWorkerService.removeByPlanId(factory, planLinePatrolDTO.getId());
        planLinePatrolDTO.getWorkers().forEach(e -> e.setPlanId(planLinePatrolDTO.getId()));
        boolean b2 = planLinePatrolWorkerService.saveBatch(factory, planLinePatrolDTO.getWorkers());

        // 清除并保存巡视点数据
        Assert.notEmpty(planLinePatrolDTO.getItemList(), "巡视点不能为空");
        boolean b3 = planLinePatrolItemService.removeDtoByPlanId(factory, planLinePatrolDTO.getId());
        boolean b4 = planLinePatrolItemService.saveDtoBatch(factory, planLinePatrolDTO.getItemList(), planLinePatrolDTO.getId());

        // 保存cron
        boolean b5 = saveCron(factory, planLinePatrolDTO);
        return b || b1 || b2 || b3 || b4 || b5;
    }

    private boolean saveCron(SessionFactory factory, PlanLinePatrolDTO detail){
        // 设置cron表达式
        if (Constants.YesOrNo.YES.equals(detail.getIsTimer())) {
            detail.getCronTask().setCron(CommonUtils.parseStrToCron(detail.getCronTask()));
        }
        // 4、设置定时任务表达式(detail.getIsTimer() = 1时，cron不能为空)
        if (Constants.YesOrNo.YES.equals(detail.getIsTimer())) {
            // 定时下达 = true，新增/更新任务
            Assert.notNull(detail.getCronTask(), "定时任务不能为空");
            CronTask cronTask = xxlJobService.getCronTaskById(detail.getId());
            if (cronTask != null) {
                // 更新任务
                xxlJobService.update(detail.getId(), detail.getCronTask().getCron(), factory.getDbEnv(), null, detail.getUpdateBy(), null);
            } else {
                // 新增任务 创建人名字[时间]巡视计划
                String jobName = detail.getUpdateBy() + "的巡视计划[" + DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm") + "]";
                xxlJobService.addSimpleJob(detail.getId(), detail.getCronTask().getCron(), factory.getDbEnv(), jobName, detail.getUpdateBy(), "generateLinePatrolOrder", 30);
            }
        } else {
            // 如果不是定时任务，则删除任务（之前为定时任务时，需要删除）
            xxlJobService.remove(detail.getId());
        }
        return true;
    }

    /**
     * 修改数据
     *
     */
    @Override
    @TaskAnnotation("update")
    public boolean update(SessionFactory factory, PlanLinePatrol planLinePatrol) {
        PlanLinePatrolMapper mapper = factory.getMapper(PlanLinePatrolMapper.class);
        return mapper.update(planLinePatrol) > 0;
    }

    @Override
    @TaskAnnotation("updateState")
    public boolean updateState(SessionFactory factory, String id, Integer state) {
        PlanLinePatrol planLinePatrol = new PlanLinePatrol();
        planLinePatrol.setId(id);
        planLinePatrol.setState(state);
        return update(factory, planLinePatrol);
    }

    @Override
    @TaskAnnotation("startOrStop")
    public PlanLinePatrolDTO startOrStop(SessionFactory factory, String id, Boolean startOrStop) {
        PlanLinePatrolDTO detailDTO = getDtoById(factory, id);

        if (Constants.YesOrNo.YES.equals(detailDTO.getIsTimer())) {
            if (startOrStop) {
                // 已启用，且定时下达
                if (!CommonUtils.isPositive(detailDTO.getAdvanceHours())) {
                    // 1、不提前 ---> 启动定时任务
                    xxlJobService.start(id);
                } else {
                    xxlJobService.start(id);
                    // 2、提前 ---> 创建一个定点执行的任务（只执行一次的任务）
                    Assert.isTrue(Constants.PATROL_PLAN_STATE.DRAFT.equals(detailDTO.getState()), "任务已处于启用状态");
                    Date firstTaskTime = getFirstExecuteTimes(detailDTO.getCronTask().getCron(), detailDTO.getAdvanceHours());
                    String executeTCron = CommonUtils.getCron(firstTaskTime);
                    CronTask cronTask = xxlJobService.getCronTaskById(id);
                    XxlJobInfo jobInfo = xxlJobService.getXxlJobInfoById(cronTask.getXxlJobId());
                    xxlJobService.addSimpleJob(String.valueOf(cronTask.getXxlJobId()), executeTCron, cronTask.getMark(), "jobId=" + cronTask.getXxlJobId() + "的临时一次性任务", detailDTO.getUpdateBy(), jobInfo.getExecutorHandler(), 30);
                    xxlJobService.start(String.valueOf(cronTask.getXxlJobId()));
                }
            } else {
                Assert.isTrue(Constants.PATROL_PLAN_STATE.USING.equals(detailDTO.getState()), "任务已处于停用状态");
                xxlJobService.pause(id);
                if (CommonUtils.isPositive(detailDTO.getAdvanceHours())) {
                    // 如果是临时任务，则移除临时任务
                    CronTask cronTask = xxlJobService.getCronTaskById(id);
                    if (cronTask != null && cronTask.getXxlJobId() != null) {
                        // 任务必须要移除成功，则否报出异常，防止脏数据
                        Assert.isTrue(xxlJobService.remove(String.valueOf(cronTask.getXxlJobId())), "提前任务移除失败");
                    }
                }
            }
        }
        Integer state = startOrStop ? 1 : 0;
        updateState(factory, id, startOrStop ? 1 : 0);
        detailDTO.setState(state);
        return detailDTO;
    }


    private Date getFirstExecuteTimes(String cron, Double hours) {
        int time = 2;
        // 下次执行的时间
        Date nextTime = null;
        while (nextTime == null || nextTime.compareTo(new Date()) <= hours * 3600 * 1000 || time > 1024) {
            List<String> executeTimes = xxlJobService.nextTriggerTime(cron, time);
            Assert.notEmpty(executeTimes, "不合法的cron表达式,请确认定时任务设定的时间,保存后重试");
            for (int i = 0, size = executeTimes.size(); i < size; i++) {
                try {
                    nextTime = DateUtils.parseDate(executeTimes.get(i), "yyyy-MM-dd HH:mm:ss");
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
                Assert.notNull(nextTime, "非法日期字符串， 要求时间格式: yyyy-MM-dd HH:mm:ss");
                if (nextTime.getTime() - hours * 3600 * 1000 > (System.currentTimeMillis() + 5 * 1000)) {
                    /** fixup: 其中5秒为预读时间，因为xxl-job会将当前到之后5秒内的任务全部取出，例如当前是13:59:55，取出了13:59:55~14:00:00的任务
                     * 但获取下一次执行时间时，发现14:00:00在当前时间13:59:55之后，误认为14:00:00是下次执行的时间，其实14:00:00是这次执行的时间，
                     * 只是xxl-job预读了后5秒的数据，导致任务提前几秒执行了。应用将14:00:00作为下次执行的时间，但xxl-job在13:59:55执行的其实是14:00:00要执行的任务，
                     * 故在14:00:00时，不会再将任务执行一次，导致下次的临时任务时间失效
                     */
                    return new Date(new Double(nextTime.getTime() - (hours * 3600 * 1000)).longValue());
                }
            }
            if (nextTime == null && executeTimes.size() == 1) {
                // 如果是定点任务， 且算上提前时间，任务执行的时间在当前时间之前
                throw new IllegalArgumentException("提前下达的时间过，该任务将无法执行");
            }
            time <<= 1;
        }
        return null;
    }

    @Override
    @TaskAnnotation("issue")
    public PlanLinePatrolDTO issue(SessionFactory factory, String id) {
        PlanLinePatrolDTO detailDTO = getDtoById(factory, id);
        Assert.isTrue(Constants.PATROL_PLAN_STATE.USING.equals(detailDTO.getState()), "计划处于草稿状态，无法下发");
        log.info("手动下发巡视计划，生成工单");
        // 如果是手动下发，则计划开始时间不将提前下发计算在内
        detailDTO.setAdvanceHours(0.0);
        boolean flag = generateLinePatrolOrder(detailDTO);
        Assert.isTrue(flag, "巡视工单生成失败");
        return detailDTO;
    }


    /**
     * 生成巡视工单
     */
    @XxlJob(value = "generateLinePatrolOrder")
    public boolean generateLinePatrolOrder(PlanLinePatrolDTO detail) {
        // 默认创建失败
        boolean flag = false;
        // 任务类型，1->手动任务，2->定时任务运，不提前，3->定时任务，提前
        Integer taskType = null;
        CronTask cronTask = null;
        // 1、获取任务详情
        if (detail != null) {
            // 1、手动生成
            taskType = 1;
        } else {
            // 2、定时任务
            try (SessionFactory factory = new SessionFactory()) {
                cronTask = xxlJobService.getCronTaskByXxlJobId((int) XxlJobHelper.getJobId());
                // 如果是定时任务，则获取的默认数据源为system，需要进行切换
                EamUser user = ThreadLocalContext.get();
                user = user == null ? new EamUser() : user;
                user.setCurrDs(cronTask.getMark());
                ThreadLocalContext.set(user);
                Assert.notNull(cronTask, "定时任务为空");
                if (CommonUtils.parse(cronTask.getBusinessId()) > 0) {
                    // 2.1、定时任务-提前（临时任务，将临时任务id作为业务id查询出定时任务）
                    XxlJobHelper.log("定时任务-提前");
                    taskType = 3;
                    cronTask = xxlJobService.getCronTaskByXxlJobId(CommonUtils.parse(cronTask.getBusinessId()));
                } else {
                    // 2.2、定时任务
                    XxlJobHelper.log("定时任务");
                    taskType = 2;
                }
                factory.setDbEnv(cronTask.getMark());
                XxlJobHelper.log("mark = {}", cronTask.getMark());
                detail = getDtoById(factory, cronTask.getBusinessId());
                Assert.notNull(detail, "巡视计划详情不能为空");
            } catch (Exception e) {
                XxlJobHelper.log(e.getMessage(), e);
                log.error(e.getMessage(), e);
            }
        }
        log.info("巡视计划详情: {}", JSON.toJSONString(detail));
        XxlJobHelper.log("巡视计划定时任务类型： {}， 巡视计划详情: {}", taskType == 1 ? "手动任务" : (taskType == 2 && detail.getAdvanceHours() == 0 ? "定时任务[不提前]" : "定时任务[提前]" + detail.getAdvanceHours() + "时"), JSON.toJSONString(detail));

        XxlJobHelper.log("巡视计划数值：taskType=" + taskType + "提前：" + detail.getAdvanceHours());
        //如果是手动下发则直接生成
        if (taskType == 1) {
            // 2、调用工单方法生成工单
            flag = createPatrolOrder(detail);
            XxlJobHelper.log("手动下发工单生成结果 = {}", flag);
        }
        //不是手动下发,则采用延迟生成策略(先生成主表,后续生成子表内容)
        if ((taskType == 2 && !CommonUtils.isPositive(detail.getAdvanceHours())) || (taskType == 3 && CommonUtils.isPositive(detail.getAdvanceHours()))) {
            // 2、调用工单方法生成工单
            XxlJobHelper.log("开始执行主表延迟生成策略");
            flag = createPatrolOrderOnly(detail);
            XxlJobHelper.log("定时任务工单生成结果 = {}", flag);
        }
        // 3、如果是提前执行的任务，调用完工单后，删除当前任务，再生成下次执行的定点任务
        if (taskType == 3) {
            XxlJobHelper.log("更新下次执行的定点任务");
            // 3.1、更新下次执行的定点任务
            XxlJobInfo jobInfo = xxlJobService.getXxlJobInfoById(cronTask.getXxlJobId());
            Date firstTaskTime = getFirstExecuteTimes(detail.getCronTask().getCron(), detail.getAdvanceHours());
            String executeTCron = CommonUtils.getCron(firstTaskTime);
            XxlJobHelper.log("jobInfo = {}, cronTask = {},detail = {}", JSON.toJSONString(jobInfo), JSON.toJSONString(cronTask), JSON.toJSONString(detail));
            xxlJobService.update(String.valueOf(cronTask.getXxlJobId()), executeTCron, cronTask.getMark(), "jobId=" + cronTask.getXxlJobId() + "的临时一次性任务", detail.getUpdateBy(), jobInfo.getExecutorHandler());
            xxlJobService.start(String.valueOf(cronTask.getXxlJobId()));
            XxlJobHelper.log("下一次临时一次性任务更新完成, 时间 = {}", executeTCron);
        }
        if (taskType == 2 && CommonUtils.isPositive(detail.getAdvanceHours())) {
            XxlJobHelper.log("开始自救策略");
            // 临时任务cronTask
            CronTask scheduleTask = xxlJobService.getCronTaskById(String.valueOf(cronTask.getXxlJobId()));
            //临时任务 xxljob
            XxlJobInfo jobInfo = xxlJobService.getXxlJobInfoById(scheduleTask.getXxlJobId());
            if (jobInfo.getTriggerStatus() == 0) {
                Date firstTaskTime = getFirstExecuteTimes(detail.getCronTask().getCron(), detail.getAdvanceHours());
                String executeTCron = CommonUtils.getCron(firstTaskTime);
                XxlJobHelper.log("[自救]jobInfo = {}, cronTask = {},detail = {}", JSON.toJSONString(jobInfo), JSON.toJSONString(cronTask), JSON.toJSONString(detail));
                xxlJobService.update(String.valueOf(cronTask.getXxlJobId()), executeTCron, cronTask.getMark(), "jobId=" + cronTask.getXxlJobId() + "的临时一次性任务", detail.getUpdateBy(), jobInfo.getExecutorHandler());
                xxlJobService.start(String.valueOf(cronTask.getXxlJobId()));
                XxlJobHelper.log("[自救]下一次临时一次性任务更新完成, 时间 = {}", executeTCron);
            }
        }
        XxlJobHelper.log("巡视计划生成-执行结果 = {}", flag);
        //清除ThreadLocal信息,防止线程污染
        ThreadLocalContext.clear();
        return flag;
    }

    /**
     * 巡视计划下发巡视工单时进行通知
     */
    public boolean createPatrolOrder(PlanLinePatrolDTO detail) {
        long now = System.currentTimeMillis();
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(ThreadLocalContext.get().getCurrDs());
            XxlJobHelper.log("当前数据源: {}", ThreadLocalContext.get().getCurrDs());
            PatrolDto patrolDto = convertor.planBasicToOrder(detail);
            List<PatrolPointVo> pointVos = new ArrayList<>();
            patrolDto.setPointVos(pointVos);
            // 默认待巡视状态
            patrolDto.setStatus(Constants.PATROL_ORDER_INIT_STATUS);
            // 1、计划主体
            // {巡视计划标题}_{YYYYMMDDHH}
            patrolDto.setTitle(detail.getName() + "_" + DateFormatUtils.format(new Date(), "yyyyMMddHH"));
            // 提前的小时数
            Double advanceHours = detail.getAdvanceHours();
            // 如果有提前时间，则往后推迟指定时间，计作计划开始时间
            Date startTime = new Date(now + (advanceHours == null ? 0 : new Double((advanceHours * 3600 * 1000)).longValue()));
            patrolDto.setPlanStartTime(startTime);
            patrolDto.setPlanEndTime(new Date(startTime.getTime() + new Double(detail.getPatrolDuration() * 3600 * 1000).longValue()));
            patrolDto.setCreateTime(new Date());
            patrolDto.setUpdateTime(new Date());
            // 2、巡视点
            List<PlanLinePatrolItemDTO> rels = detail.getItemList();
            List<String> riskIds = rels.stream()
                    .filter(e -> "risk".equals(e.getCategory()))
                    .map(PlanLinePatrolItemDTO::getPatrolPointId)
                    .collect(Collectors.toList());
            Map<String, List<PatrolEquipmentVo>> equipmentMap = new HashMap<>();
            if (!CollectionUtils.isEmpty(riskIds)) {
                Object data = orderFeign.listByRiskIds(riskIds).getData();
                if (data != null) {
                    equipmentMap = ((List<Object>) data).stream()
                            .map(e -> {
                                PatrolEquipmentVo patrolEquipmentVo = new PatrolEquipmentVo();
                                JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(e));
                                patrolEquipmentVo.setEquipmentId( jsonObject.getString("equipmentId") );
                                patrolEquipmentVo.setName( jsonObject.getString("equipmentName") );
                                patrolEquipmentVo.setPatrolPointId( jsonObject.getString("riskId") );
                                return patrolEquipmentVo;
                            }).collect(Collectors.groupingBy(PatrolEquipmentPo::getPatrolPointId));
                }
            }

            // 所有杆塔id（要从杆塔中获取经纬度数据） --> 由巡视工单懒加载经纬度
            for (PlanLinePatrolItemDTO rel : rels) {
                List<PatrolEquipmentVo> patrolEquipmentVos;
                if ("risk".equals(rel.getCategory()) && equipmentMap != null) {
                    patrolEquipmentVos = equipmentMap.getOrDefault(rel.getPatrolPointId(), new ArrayList<>());
                } else {
                    patrolEquipmentVos = convertor.planEquimToPatrolList(rel.getEquipmentList());
                }

                for (PatrolEquipmentVo patrolEquipmentVo : patrolEquipmentVos) {
                    patrolEquipmentVo.setEquipmentItemPos(convertor.itemsToOrderItems(rel.getCheckList()));
                    // 给检查项和记录项添加设备id
                    if (!CollectionUtils.isEmpty(patrolEquipmentVo.getEquipmentItemPos())) {
                        for (PatrolEquipmentItemPo equipmentItemPo : patrolEquipmentVo.getEquipmentItemPos()) {
                            equipmentItemPo.setPatrolEquipmentId(patrolEquipmentVo.getEquipmentId());
                        }
                    }
                    patrolEquipmentVo.setEquipmentParamsPos(convertor.recordsToParams(rel.getRecordList()));
                    if (!CollectionUtils.isEmpty(patrolEquipmentVo.getEquipmentParamsPos())) {
                        for (PatrolEquipmentParamsPo equipmentParamsPo : patrolEquipmentVo.getEquipmentParamsPos()) {
                            equipmentParamsPo.setPatrolEquipmentId(patrolEquipmentVo.getEquipmentId());
                        }
                    }
                }

                PatrolPointVo patrolPointVo = convertor.pointDetailToOrderPoint(rel);
                patrolPointVo.setEquipmentVos(patrolEquipmentVos);

                if (patrolPointVo.getName() != null && !StrUtil.containsAny(patrolPointVo.getName(), "#", "~")) {
                    if (patrolEquipmentVos.size() == 1) {
                        patrolPointVo.setName(patrolPointVo.getName() + patrolEquipmentVos.get(0).getName());
                    } else if (patrolEquipmentVos.size() > 1) {
                        String firstName = patrolEquipmentVos.get(0).getName();
                        String lastName = patrolEquipmentVos.get(patrolEquipmentVos.size()-1).getName();
                        patrolPointVo.setName(patrolPointVo.getName() + firstName + "~" + lastName);
                    }
                }
                patrolPointVo.setRange(rel.getInvalidRange());
                pointVos.add(patrolPointVo);
            }
            if (!Constants.StaffingWay.map.containsKey(detail.getStaffingWay())) {
                // 指定巡视人员
                List<PatrolUserPo> userPos = new ArrayList<>();
                List<PlanLinePatrolWorker> workers = detail.getWorkers();
                Stream.iterate(0, i -> i + 1).limit(workers.size()).forEach(i -> {
                    PlanLinePatrolWorker worker = workers.get(i);
                    userPos.add(new PatrolUserPo(worker.getWorkerId(), worker.getWorkerName(), i + 1));
                });
                patrolDto.setUserPos(userPos);
            }
            //处理生成的工单数据
            dealPatrolDto(patrolDto);
            log.info("生成巡视工单， dto = {}", JSON.toJSONString(patrolDto));
            Response<String> response = orderFeign.insertOrUpdateLine(patrolDto);
            log.info("生成巡视工单响应， response = {}", response.toJson());
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 解决定时任务生成经常失败问题
     * 采用先生成主表巡视单的内容，后续打开详情的时候才生成具体的子表内容
     *
     * @param detail
     * @return
     */
    public boolean createPatrolOrderOnly(PlanLinePatrolDTO detail) {
        long now = System.currentTimeMillis();
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(ThreadLocalContext.get().getCurrDs());
            XxlJobHelper.log("开始创建子表数据,当前数据源: {}", ThreadLocalContext.get().getCurrDs());
            PatrolDto patrolDto = convertor.planBasicToOrder(detail);
            // 默认待巡视状态
            patrolDto.setStatus(Constants.PATROL_ORDER_INIT_STATUS);
            // 1、计划主体
            // {巡视计划标题}_{YYYYMMDDHH}
            patrolDto.setTitle(detail.getName() + "_" + DateFormatUtils.format(new Date(), "yyyyMMddHH"));
            // 提前的小时数
            Double advanceHours = detail.getAdvanceHours();
            // 如果有提前时间，则往后推迟指定时间，计作计划开始时间
            Date startTime = new Date(now + (advanceHours == null ? 0 : new Double((advanceHours * 3600 * 1000)).longValue()));
            patrolDto.setPlanStartTime(startTime);
            patrolDto.setPlanEndTime(new Date(startTime.getTime() + new Double(detail.getPatrolDuration() * 3600 * 1000).longValue()));
            patrolDto.setCreateTime(new Date());
            patrolDto.setUpdateTime(new Date());

            patrolDto.setId(null);
            patrolDto.setPointNum(detail.getItemList().size());
            patrolDto.setInsertFlag(1);

            if (!Constants.StaffingWay.map.containsKey(detail.getStaffingWay())) {
                // 指定巡视人员
                List<PatrolUserPo> userPos = new ArrayList<>();
                List<PlanLinePatrolWorker> workers = detail.getWorkers();
                Stream.iterate(0, i -> i + 1).limit(workers.size()).forEach(i -> {
                    PlanLinePatrolWorker worker = workers.get(i);
                    userPos.add(new PatrolUserPo(worker.getWorkerId(), worker.getWorkerName(), i + 1));
                });
                patrolDto.setUserPos(userPos);
            }

            log.info("生成巡视工单主表， dto = {}", JSON.toJSONString(patrolDto));
            Response<Object> response = orderFeign.insertOrderLine(patrolDto);
            log.info("生成巡视工单主表响应， response = {}", response.toJson());
            XxlJobHelper.log("主表创建成功");
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            XxlJobHelper.log("主表创建时报错,错误信息：", e.getStackTrace());
            return false;
        }
    }

    /*对部分字段信息进行抹除*/
    private void dealPatrolDto(PatrolDto patrolDto) {
        if (Objects.isNull(patrolDto) || org.apache.commons.collections4.CollectionUtils.isEmpty(patrolDto.getPointVos())) {
            return;
        }
        patrolDto.setId(null);
        List<PatrolPointVo> pointVos = patrolDto.getPointVos();

        pointVos.forEach(p -> {
            p.setId(null);
            List<PatrolEnvCheckVo> envCheckVos = p.getEnvCheckVos();
            if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(envCheckVos)) {
                envCheckVos.forEach(t -> t.setId(null));
            }
            List<PatrolEquipmentVo> equipmentVos = p.getEquipmentVos();
            if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(equipmentVos)) {
                equipmentVos.forEach(t -> {
                    t.setId(null);
                    List<PatrolEquipmentItemPo> equipmentItemPos = t.getEquipmentItemPos();
                    if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(equipmentItemPos)) {
                        equipmentItemPos.forEach(itemPo -> itemPo.setId(null));
                    }
                    List<PatrolEquipmentParamsPo> equipmentParamsPos = t.getEquipmentParamsPos();
                    if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(equipmentParamsPos)) {
                        equipmentParamsPos.forEach(paramsPo -> paramsPo.setId(null));
                    }
                });
            }
        });
    }

    /**
     * 通过主键删除数据
     *
     */
    @Override
    @TaskAnnotation("deleteById")
    public boolean deleteById(SessionFactory factory, String id) {
        PlanLinePatrolMapper mapper = factory.getMapper(PlanLinePatrolMapper.class);
        return mapper.hidden(id) > 0;
    }

    @Override
    @TaskAnnotation("getPlanPatrolMsg")
    public PatrolDto getPlanPatrolMsg(SessionFactory factory, String planId, String orderCreateTime) {
        PlanLinePatrolDTO detail = getDtoById(factory, planId);
        long now = System.currentTimeMillis();
        try {
            XxlJobHelper.log("当前数据源: {}", ThreadLocalContext.get().getCurrDs());
            PatrolDto patrolDto = convertor.planBasicToOrder(detail);
            List<PatrolPointVo> pointVos = new ArrayList<>();
            patrolDto.setPointVos(pointVos);
            // 默认待巡视状态
            patrolDto.setStatus(Constants.PATROL_ORDER_INIT_STATUS);
            // 1、计划主体
            // {巡视计划标题}_{YYYYMMDDHH}
            patrolDto.setTitle(detail.getName() + "_" + DateFormatUtils.format(new Date(), "yyyyMMddHH"));
            // 提前的小时数
            Double advanceHours = detail.getAdvanceHours();
            // 如果有提前时间，则往后推迟指定时间，计作计划开始时间
            Date startTime = new Date(now + (advanceHours == null ? 0 : new Double((advanceHours * 3600 * 1000)).longValue()));
            patrolDto.setPlanStartTime(startTime);
            patrolDto.setPlanEndTime(new Date(startTime.getTime() + new Double(detail.getPatrolDuration() * 3600 * 1000).longValue()));
            patrolDto.setCreateTime(new Date());
            patrolDto.setUpdateTime(new Date());
            // 2、巡视点
            List<PlanLinePatrolItemDTO> rels = detail.getItemList();
            List<String> riskIds = rels.stream()
                    .filter(e -> "risk".equals(e.getCategory()))
                    .map(PlanLinePatrolItemDTO::getPatrolPointId)
                    .collect(Collectors.toList());
            Map<String, List<PatrolEquipmentVo>> equipmentMap = new HashMap<>();
            if (!CollectionUtils.isEmpty(riskIds)) {
                Object data = orderFeign.listByRiskIds(riskIds).getData();
                if (data != null) {
                    equipmentMap = ((List<Object>) data).stream()
                            .map(e -> {
                                PatrolEquipmentVo patrolEquipmentVo = new PatrolEquipmentVo();
                                JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(e));
                                patrolEquipmentVo.setEquipmentId( jsonObject.getString("equipmentId") );
                                patrolEquipmentVo.setName( jsonObject.getString("equipmentName") );
                                patrolEquipmentVo.setPatrolPointId( jsonObject.getString("riskId") );
                                return patrolEquipmentVo;
                            }).collect(Collectors.groupingBy(PatrolEquipmentPo::getPatrolPointId));
                }
            }

            // 所有杆塔id（要从杆塔中获取经纬度数据） --> 由巡视工单懒加载经纬度
            for (PlanLinePatrolItemDTO rel : rels) {
                List<PatrolEquipmentVo> patrolEquipmentVos;
                if ("risk".equals(rel.getCategory()) && equipmentMap != null) {
                    patrolEquipmentVos = equipmentMap.getOrDefault(rel.getPatrolPointId(), new ArrayList<>());
                } else {
                    patrolEquipmentVos = convertor.planEquimToPatrolList(rel.getEquipmentList());
                }

                for (PatrolEquipmentVo patrolEquipmentVo : patrolEquipmentVos) {
                    patrolEquipmentVo.setEquipmentItemPos(convertor.itemsToOrderItems(rel.getCheckList()));
                    // 给检查项和记录项添加设备id
                    if (!CollectionUtils.isEmpty(patrolEquipmentVo.getEquipmentItemPos())) {
                        for (PatrolEquipmentItemPo equipmentItemPo : patrolEquipmentVo.getEquipmentItemPos()) {
                            equipmentItemPo.setPatrolEquipmentId(patrolEquipmentVo.getEquipmentId());
                        }
                    }
                    patrolEquipmentVo.setEquipmentParamsPos(convertor.recordsToParams(rel.getRecordList()));
                    if (!CollectionUtils.isEmpty(patrolEquipmentVo.getEquipmentParamsPos())) {
                        for (PatrolEquipmentParamsPo equipmentParamsPo : patrolEquipmentVo.getEquipmentParamsPos()) {
                            equipmentParamsPo.setPatrolEquipmentId(patrolEquipmentVo.getEquipmentId());
                        }
                    }
                }

                PatrolPointVo patrolPointVo = convertor.pointDetailToOrderPoint(rel);
                patrolPointVo.setEquipmentVos(patrolEquipmentVos);
                if (patrolPointVo.getName() != null && !patrolPointVo.getName().contains("#|~")) {
                    if (patrolEquipmentVos.size() == 1) {
                        patrolPointVo.setName(patrolPointVo.getName() + patrolEquipmentVos.get(0).getName());
                    } else if (patrolEquipmentVos.size() > 1) {
                        String firstName = patrolEquipmentVos.get(0).getName();
                        String lastName = patrolEquipmentVos.get(patrolEquipmentVos.size()-1).getName();
                        patrolPointVo.setName(patrolPointVo.getName() + firstName + "~" + lastName);
                    }
                }
                patrolPointVo.setRange(rel.getInvalidRange());
                pointVos.add(patrolPointVo);
            }
            //处理生成的工单数据
            dealPatrolDto(patrolDto);
            return patrolDto;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    @Override
    @TaskAnnotation(value = "queryDetailByPatrolPointId")
    public List<PlanLinePatrolItemDTO> queryDetailByPatrolPointId(SessionFactory factory, String patrolPointId) {
        Assert.notNull(patrolPointId,"巡视线路id不能为空！");
        PlanLinePatrolItemMapper patrolItemMapper = factory.getMapper(PlanLinePatrolItemMapper.class);
        List<PlanLinePatrolItem> patrolItemList = patrolItemMapper.queryDetailByPatrolPointId(patrolPointId);
        if (CollectionUtils.isEmpty(patrolItemList)){
            return new ArrayList<>();
        }
        List<PlanLinePatrolItemDTO> linePatrolItems = patrolItemList.stream()
                .map(p->BeanUtil.copyProperties(p,PlanLinePatrolItemDTO.class))
                .collect(Collectors.toMap(PlanLinePatrolItem::getItemValue, p -> p, (o1, o2) -> o1))
                .values().stream().collect(Collectors.toList());

        List<PlanLinePatrolEquipment> equipmentList = patrolItemList.stream().map(p -> {
            PlanLinePatrolEquipment patrolEquipment = BeanUtil.copyProperties(p, PlanLinePatrolEquipment.class);
            patrolEquipment.setPatrolPointType(p.getItemValue());
            return patrolEquipment;
        }).collect(Collectors.toList());

        linePatrolItems.forEach(d -> {
            List<PlanLinePatrolEquipment> equipments = new ArrayList<>();
            equipmentList.forEach(e -> {
                if (d.getPatrolPointId().equals(e.getPatrolPointId()) && Objects.equals(d.getItemValue(),(e.getPatrolPointType()))) {
                    equipments.add(e);
                }
            });
            d.setEquipmentList(equipments);
        });

        return linePatrolItems;
    }
}
